#include <stddef.h>
#include <stdexcept>

#include "HashTable.h"
#include "HashTableIterator.h"
#include <stdio.h>
#include <assert.h>

// el tamaño inicial de la tabla
const unsigned int DEFAULT_TABLE_SIZE = 4;
// el factor de carga máximo de la tabla
const float DEFAULT_THRESHOLD = 0.75;

// si es true se hace delete de claves al destruir la tabla o al borrar elementos
const bool DELETE_KEYS = true;

HashTable::HashTable():
        threshold(DEFAULT_THRESHOLD),
        maxSize(DEFAULT_TABLE_SIZE * DEFAULT_THRESHOLD),
        tableSize(DEFAULT_TABLE_SIZE),
        size(0)
{
    table = new LinkedHashEntry*[tableSize];
    for (unsigned int i = 0; i < tableSize; i++)
        table[i] = NULL;
}

void HashTable::resize() {
    unsigned int oldTableSize = tableSize;
    tableSize *= 2;
    maxSize = (unsigned int) (tableSize * threshold);
    LinkedHashEntry **oldTable = table;
    table = new LinkedHashEntry*[tableSize];
    
    for (unsigned int i = 0; i < tableSize; i++)
        table[i] = NULL;

    size = 0;
    for (unsigned int hash = 0; hash < oldTableSize; hash++)
        if (oldTable[hash] != NULL) {
            LinkedHashEntry *oldEntry = NULL;
            LinkedHashEntry *entry = oldTable[hash];
            while (entry != NULL) {
                put(entry->getKey(), entry->getValue());
                oldEntry = entry;
                entry = entry->getNext();
                delete oldEntry;
            }

        }
    delete[] oldTable;
}

ICollectible *HashTable::get(HashedKey *key) {
    if (key == NULL)
        throw std::invalid_argument("key es NULL");

    unsigned int hash = ((unsigned)key->hash()) % tableSize;

    if (table[hash] == NULL)
        return NULL;
    else {
        LinkedHashEntry *entry = table[hash];

        while (entry != NULL && !(entry->getKey()->equals(key)))
            entry = entry->getNext();
        if (entry == NULL)
            return NULL;
        else
            return entry->getValue();
    }
}

void HashTable::put(HashedKey *key, ICollectible *value) {
    if (key == NULL)
        throw std::invalid_argument("key es NULL");
    if (value == NULL)
        throw std::invalid_argument("value es NULL");

    int hash = ((unsigned)key->hash()) % tableSize;

    if (table[hash] == NULL) {
        table[hash] = new LinkedHashEntry(key, value);
        size++;
    } else {
        LinkedHashEntry *entry = table[hash];

        while (entry->getNext() != NULL && !(entry->getKey()->equals(key)))
            entry = entry->getNext();
        if (entry->getKey()->equals(key)){
            if(DELETE_KEYS){ // hay que borrar la key vieja
                delete entry->getKey();
                entry->setKey(key);
            }
            entry->setValue(value);
        } else {
            entry->setNext(new LinkedHashEntry(key, value));
            size++;
        }
    }

    if (size >= maxSize)
        resize();
}

void HashTable::remove(HashedKey *key) {
    unsigned int hash = ((unsigned)key->hash()) % tableSize;

    if (table[hash] != NULL) {
        LinkedHashEntry *prevEntry = NULL;
        LinkedHashEntry *entry = table[hash];

        while (entry->getNext() != NULL && !(entry->getKey()->equals(key))) {
            prevEntry = entry;
            entry = entry->getNext();
        }

        if (entry->getKey()->equals(key)) {
            if (prevEntry == NULL) {
                LinkedHashEntry *nextEntry = entry->getNext();

                if(DELETE_KEYS) // se borra la claves del diccionario
                    delete entry->getKey();        
                delete entry;
                table[hash] = nextEntry;
            } else {
                LinkedHashEntry *next = entry->getNext();
                if(DELETE_KEYS) // se borra la claves del diccionario
                    delete entry->getKey();
                delete entry;
                prevEntry->setNext(next);
            }
            size--;
        }
    }
}

int HashTable::getSize()
{
    return size;
}

void HashTable::getKeys(HashedKey **&resArray)
{
    resArray = new HashedKey *[size];
    unsigned int resArrayIdx = 0;
    
    for (unsigned int hash = 0; hash < tableSize; hash++)
        if (table[hash] != NULL) {
            LinkedHashEntry *entry = table[hash];

            while (entry != NULL) {
                resArray[resArrayIdx] = entry->getKey();
                entry = entry->getNext();
                ++resArrayIdx;
            }
        }
    
    assert(resArrayIdx == size);
}

HashTableIterator *HashTable::getIterator()
{

    return new HashTableIterator(*this);
}

HashTable::~HashTable() {
    for (unsigned int hash = 0; hash < tableSize; hash++)
        if (table[hash] != NULL) {
            LinkedHashEntry *prevEntry = NULL;
            LinkedHashEntry *entry = table[hash];

            while (entry != NULL) {
                prevEntry = entry;
                entry = entry->getNext();
                if(DELETE_KEYS) // se borra la claves del diccionario
                    delete prevEntry->getKey();
                delete prevEntry;
            }
        }
    delete[] table;
}
